/**
 * 
 */
package pa2;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.TreeMap;

/**
 * @author Joerg Poecher
 *
 */
public class MyKVPeer extends UnicastRemoteObject
implements IMyKVPeer {

	private static final long serialVersionUID = 1L;

	private int accessCounter;
	private int peerNumber;
	private TreeMap<Long, String> storage;
	private IMyKVPeer leftChild;
	private IMyKVPeer rightChild;

	protected MyKVPeer() throws RemoteException {
		super();
		this.storage = new TreeMap<Long, String>();
		this.leftChild = null;
		this.rightChild = null;
		this.accessCounter = 0;
	}

	

	public static void main(String[] args) {
		String host = "localhost";
		String port = Integer.toString(Registry.REGISTRY_PORT);
		
		try {
			MyKVPeer peer = new MyKVPeer();
			
			System.out.println("Looking up MyKVService at " + host + ":" + port);
			IMyKVCoordinator server = (IMyKVCoordinator) Naming.lookup("//" + host + ":" + port + "/" + IMyKVCoordinator.SERVICENAME);
			peer.setPeerNumber(server.getID());
			
			String connection = "//localhost:" + port + "/" + IMyKVPeer.SERVICENAME + peer.getPeerNumber(); 
			Naming.rebind(connection, peer);				
			System.out.println("Bound Peer RMI object in local registry at port " + port);
			server.register(peer);

		} catch (MalformedURLException e) {
			System.out.println("Probleme mit der RMI URL...");
			e.printStackTrace();
		} catch (RemoteException e) {
			System.out.println("Probleme mit dem RMI Objekt...");
			e.printStackTrace();
		} catch (NotBoundException e) {
			System.out.println("RMI Objekt nicht gebunden...");
			e.printStackTrace();
		}
	}


	// Getters and Setters

	

	public int getPeerNumber() throws RemoteException {
		return this.peerNumber;
	}

	public void setPeerNumber(int peerNumber) {
		this.peerNumber = peerNumber;
	}
	
	private void incAccessCounter() {
		this.accessCounter++;
	}

	public int getAccessCounter() {
		return this.accessCounter;
	}


	// dummy method
	public String say() throws RemoteException {
		String response = "";
		if (peerNumber == 0) {
			response += "I am root    ('Nr: " + peerNumber + "'), ";			
		} else {
			response += "I am a child ('Nr: " + peerNumber + "'), ";
		}
		response += "i only know " + this.leftChild.getPeerNumber()  
				+ " and " + this.rightChild.getPeerNumber();
		return response;
	}

	public long insert(MyKVMessage msg) throws RemoteException {
		incAccessCounter();
		System.out.println("Hop " + msg.getHopCount() + " for key: " + msg.getKey());
		if (msg.getHopCount() < NUM_OF_MAX_HOPS) {
			msg.incHopCount();

			long med = (msg.getRange()[0] + msg.getRange()[1]) / 2;
			if ( (msg.getKey() >= msg.getRange()[0]) && (msg.getKey() < med) ) {
				msg.setRange(new long[] {msg.getRange()[0], med});
				return leftChild.insert(msg);
			}
			if ( (msg.getKey() >= med) && (msg.getKey() < msg.getRange()[1]) ) {
				msg.setRange(new long[] {med, msg.getRange()[1] });
				return rightChild.insert(msg);
			}
		}
		return insertEntry(msg.getKey(), msg.getValue());
	}

	public String lookup(MyKVMessage msg) throws RemoteException {
		incAccessCounter();
		System.out.println("Hop " + msg.getHopCount() + " at Peer " + getPeerNumber() + " | key: " + msg.getKey());
		if (msg.getHopCount() < NUM_OF_MAX_HOPS) {
			msg.incHopCount();

			long med = (msg.getRange()[0] + msg.getRange()[1]) / 2;
			if ( (msg.getKey() >= msg.getRange()[0]) && (msg.getKey() < med) ) {
				msg.setRange(new long[] {msg.getRange()[0], med});
				return leftChild.lookup(msg);
			}
			if ( (msg.getKey() >= med) && (msg.getKey() < msg.getRange()[1]) ) {
				msg.setRange(new long[] {med, msg.getRange()[1] });
				return rightChild.lookup(msg);
			}
		}
		return lookupEntry(msg.getKey());
	}

	public boolean delete(MyKVMessage msg) throws RemoteException {
		incAccessCounter();
		System.out.println("Hop " + msg.getHopCount() + " at Peer " + getPeerNumber() + " | key: " + msg.getKey());
		if (msg.getHopCount() < NUM_OF_MAX_HOPS) {
			msg.incHopCount();

			long med = (msg.getRange()[0] + msg.getRange()[1]) / 2;
			if ( (msg.getKey() >= msg.getRange()[0]) && (msg.getKey() < med) ) {
				msg.setRange(new long[] {msg.getRange()[0], med});
				return leftChild.delete(msg);
			}
			if ( (msg.getKey() >= med) && (msg.getKey() < msg.getRange()[1]) ) {
				msg.setRange(new long[] {med, msg.getRange()[1] });
				return rightChild.delete(msg);
			}
		}
		return deleteEntry(msg.getKey());
	}

	public int getStorageSize() throws RemoteException {
		return storage.size();
	}


	/* 
	 * Methods for locally accessing the data storage 
	 * (lookup, insert & delete Entries)
	 *  
	 */

	private synchronized long insertEntry(long key, String value) {
		System.out.println("storing key/value: " + key + "/" + value);
		if (!storage.containsKey(key)) {
			storage.put(key, value);
			return this.peerNumber;
		}
		return -1;
	}

	private synchronized String lookupEntry(long key) {
		// returns the value according to the key, or null otherwise
		System.out.println("fetching value of: " + key);
		return storage.get(key);
	}

	private synchronized boolean deleteEntry(long key) {
		System.out.println("trying to delete: " + key);
		if ( storage.remove(key) != null ) {
			return true;
		}
		return false;
	}


	public void setLeftChild(IMyKVPeer peer) throws RemoteException {
		this.leftChild = peer;
	}

	public void setRightChild(IMyKVPeer peer) throws RemoteException {
		this.rightChild = peer;
	}



}
